/**
 * @author Brian Alker
 * @author Edmundo
 */

import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
import java.net.SocketTimeoutException;
import java.nio.ByteBuffer;
import java.util.Date;

/**
 * This class implements the Sender in the project specifications.
 */

public class UDPClient {
	private static InetAddress IPAddress;
   
   /**
    * Generates a Poisson-distributed random variable with mean as given.
    * @param mean
    * @return
    */
   static private int genPoisson(int mean) {
      double L = Math.exp(-1 * mean);
      int k = 0;
      double p = 1;
      do {
         k++;
         double u = Math.random();
         p = p * u;
      } while (p > L);
      return k;
   }

   /**
    * Sends the given payload
    * 
    * @param sendData
    * @throws Exception
    */	
   static private void sendPacket(byte[] sendData) throws Exception {
      DatagramSocket clientSocket = new DatagramSocket();
      DatagramPacket sendPacket = new DatagramPacket(sendData, sendData.length,
            IPAddress, 9876);
      clientSocket.send(sendPacket);
      clientSocket.close();
      
//      DatagramSocket receiverSocket = new DatagramSocket(9101);
//      DatagramPacket receivePacket = new DatagramPacket(receiveData,
//            receiveData.length);
//      receiverSocket.receive(receivePacket);
//      String modifiedSentence = new String(receivePacket.getData());
//      System.out.println("FROM SERVER:" + modifiedSentence);
//      receiverSocket.close();
   }
   
   private static void sendPacketARQ(byte[] sendData, int seqNum, DatagramSocket receiverSocket, int sendRate) throws Exception {
      sendPacket(sendData);
      
      byte receiveData[] = new byte[128];
      DatagramPacket receivePacket = new DatagramPacket(receiveData, receiveData.length);
      while (true) {
         try {
            long startTime = (new Date()).getTime();
            receiverSocket.receive(receivePacket);
            
            byte ackSeqBytes[] = new byte[4];
            System.arraycopy(receivePacket.getData(), 8, ackSeqBytes, 0, 4);
            int ackSeqNum = UDPServer.byteArrayToInt(ackSeqBytes);
            if (ackSeqNum == seqNum) {
               long endTime = (new Date()).getTime();
               int wait = genPoisson(sendRate);
               if(endTime - startTime >= wait) {
                  return;
               } else {
                  Thread.sleep(wait - (endTime - startTime));
                  return;
               }
            }
         } catch (SocketTimeoutException e) {
            sendPacket(sendData);         
         }
      }
   }

   /**
    *  Takes input from command line and repeatedly sends the data to the server
    * (currently sends a sentence) 10,000 times
    * @param args arg0-3 are the 4 octets of the dest IP. 
    * arg4 is the dest port. 
    * arg5 is the send rate. 
    * arg6 is the total number of packets to send. 
    * arg7 represents the configuration.  
    * arg8 is the port it listens on for an ack. 
    * arg9 specifies whether or not to use ARQ. 
    * arg10 is the filename of the image to send; 
    * junk packets will be sent if no name is provided. 
    * The configuration refers to the case where either the server is on the localhost or not
    * arg[7] <- isLocal?
    */
   public static void main(String args[]) throws Exception {
      int destPort = Integer.parseInt(args[4]);
      int ackPort = Integer.parseInt(args[8]);
      int sendRate = Integer.parseInt(args[5]);      
      if(args[7] == "true"){
          IPAddress = InetAddress.getByName("localhost");
      } else{
    	  byte[] newbyte = {(byte)Integer.parseInt(args[0]),(byte)Integer.parseInt(args[1]),(byte)Integer.parseInt(args[2]),(byte)Integer.parseInt(args[3])};
    	  IPAddress = InetAddress.getByAddress(newbyte);
      }

      boolean useARQ = Boolean.parseBoolean(args[9]);
      DatagramSocket receiverSocket = new DatagramSocket(ackPort);
      receiverSocket.setSoTimeout(100);

      if(args.length == 11) {
         File file = new File(args[10]);
         FileInputStream fis = new FileInputStream(file);
         byte buf[] = new byte[104];
         int seqNum = 1;
         int readNum = 0;
         
         while (readNum != -1) {
            readNum = fis.read(buf);
            byte[] payload = new byte[128];
            payload[0] = (byte)Integer.parseInt(args[0]); 
            payload[1] = (byte)Integer.parseInt(args[1]); 
            payload[2] = (byte)Integer.parseInt(args[2]); 
            payload[3] = (byte)Integer.parseInt(args[3]);  //Bytes 0-3 are dest IP
            System.arraycopy(ByteBuffer.allocate(4).putInt(destPort).array(), 0, payload, 4, 4); //Bytes 4-7 are dest port
            System.arraycopy(ByteBuffer.allocate(4).putInt(seqNum).array(), 0, payload, 8, 4);  //Bytes 8-11 are packet seqnum
            System.arraycopy(ByteBuffer.allocate(8).putLong((new Date()).getTime()).array(), 0, payload, 12, 8);  //Bytes 12-19 are timestamp
            System.arraycopy(ByteBuffer.allocate(4).putInt(readNum).array(), 0, payload, 20, 4);  //Bytes 20-23 are the number of valid bytes in the message
            System.arraycopy(buf, 0, payload, 24, buf.length);
            
            if(useARQ) {
               sendPacketARQ(payload, seqNum, receiverSocket, sendRate);
            } else {
               sendPacket(payload);
               Thread.sleep(genPoisson(sendRate));
            }            
            seqNum++;
         }
      } else if(args.length == 10) {
         for(int seqNum=1; seqNum<=Integer.parseInt(args[6]); seqNum++) {
            byte[] payload = new byte[128];
            byte[] sentenceBytes = ("Time: " + (new Date()).getTime() + " Seqnum: " + seqNum + " Sending to port " + destPort).getBytes();
   //         System.out.println("SENT: " + sentence + " " + seqNum);
            payload[0] = (byte)Integer.parseInt(args[0]); 
            payload[1] = (byte)Integer.parseInt(args[1]); 
            payload[2] = (byte)Integer.parseInt(args[2]); 
            payload[3] = (byte)Integer.parseInt(args[3]);  //Bytes 0-3 are dest IP
            System.arraycopy(ByteBuffer.allocate(4).putInt(destPort).array(), 0, payload, 4, 4); //Bytes 4-7 are dest port
            System.arraycopy(ByteBuffer.allocate(4).putInt(seqNum).array(), 0, payload, 8, 4);  //Bytes 8-11 are packet seqnum
            System.arraycopy(ByteBuffer.allocate(8).putLong((new Date()).getTime()).array(), 0, payload, 12, 8);  //Bytes 12-19 are timestamp
            System.arraycopy(ByteBuffer.allocate(4).putInt(1).array(), 0, payload, 20, 4);  //Bytes 20-23 are the number of valid bytes in the message
            System.arraycopy(sentenceBytes, 0, payload, 24, sentenceBytes.length);  //Remainder is used for the actual payload
            if(useARQ) {
               sendPacketARQ(payload, seqNum, receiverSocket, sendRate);
            } else {
               sendPacket(payload);
               Thread.sleep(genPoisson(sendRate));
            }
         }
      }
   }
}